//
//	DirList by J Brown
//	The listbox must have the following styles set:
//	LBS_OWNERDRAWFIXED, LBS_HASSTRINGS, 
//	LBS_DISABLENOSCROLL, LBS_NOTIFY, LBS_NOINTEGRALHEIGHT
//
//	Also: WS_VSCROLL, WS_HSCROLL for scrollbar support
//
#include <windows.h>
#include <commctrl.h>
#include <shlobj.h>
#include <shellapi.h>
#include "DirList.h"

#include "resource.h"

#define LB_HORIZONTAL_SCROLLING

HIMAGELIST GetSystemImageList();

#define CX_OFFSET 4

#ifndef ODS_NOFOCUSRECT
#define ODS_NOFOCUSRECT     0x0200
#endif

int DriveList_GetLineHeight(HWND, HFONT, int nBmpHeight);

#define FOLDER_CLOSED 0
#define FOLDER_OPENED 1

extern HINSTANCE hInstance;		//Application global instance

static HIMAGELIST hDirImgList;

//indices into the image list.
static int nImgFolderClosed = 0;
static int nImgFolderOpened = 1;
static int nBitmapHeight    = 16;
static int nBitmapWidth     = 16;
static int nBitmapXSpace    = 4;		// horz space between bitmap and text start
static int nBitmapYOff = 0;				// y-adjustment when text is taller than bitmap

typedef struct
{
	WNDPROC oldproc;
} DirList;

void SetItemState(HWND hwndList, int idx, int level, int state)
{
	SendMessage(hwndList, LB_SETITEMDATA, idx, MAKELONG(level, state));
}

void GetItemState(HWND hwndList, int idx, int *level, int *state)
{
	LONG val;
	val = SendMessage(hwndList, LB_GETITEMDATA, idx, 0);
	*level = LOWORD(val);
	*state = HIWORD(val);
}

static HIMAGELIST GetSystemImageList()
{
	SHFILEINFO  sfi;
	return (HIMAGELIST)SHGetFileInfo(_T("C:\\"), 0, &sfi, sizeof(SHFILEINFO), SHGFI_SMALLICON|SHGFI_SYSICONINDEX);
}

int DirList_GetLineHeight(HWND hwndCombo, HFONT hFont, int nBitmapHeight)
{
	TEXTMETRIC      tm;
	HANDLE          hOldFont = NULL;
	HDC             hdc;

	int nTextHeight;
	int nLineHeight;

	hdc = GetDC(hwndCombo);

	hOldFont = SelectObject(hdc, hFont);
	GetTextMetrics(hdc, &tm);
	SelectObject(hdc, hOldFont);
	ReleaseDC(hwndCombo, hdc);

	nTextHeight = tm.tmHeight + tm.tmInternalLeading;

	nLineHeight = max(nBitmapHeight, nTextHeight);

	return nLineHeight;
}

void DirList_UpdateWidth(HWND hwndList)
{
	HANDLE hOld;
	HDC hdc; 
	HFONT hFont;
	RECT rect;
	int i;
	int count;
	int width = 0;

	int nLevel, nState;
	
	hdc   = GetDC(hwndList);
	hFont = (HFONT)SendMessage(hwndList, WM_GETFONT, 0, 0);
	
	hOld = SelectObject(hdc, hFont);
	
    count = SendMessage(hwndList, LB_GETCOUNT, 0, 0);
	
	for(i = 0; i < count; i++)
	{
		SIZE sz;
		TCHAR ach[MAX_PATH];

		SendMessage(hwndList, LB_GETTEXT, i, (LONG)ach);
		GetItemState(hwndList, i, &nLevel, &nState);
	
		GetTextExtentPoint32(hdc, ach, lstrlen(ach), &sz);
		sz.cx += 4 * GetSystemMetrics(SM_CXBORDER) + 
			nBitmapXSpace + nBitmapWidth + nLevel * CX_OFFSET;

		if(sz.cx > width)
			width = sz.cx;
	}

	GetClientRect(hwndList, &rect);
	
	if(width < rect.right)	ShowScrollBar(hwndList, SB_HORZ, FALSE);
	else					ShowScrollBar(hwndList, SB_HORZ, TRUE);

	SendMessage(hwndList, LB_SETHORIZONTALEXTENT, width, 0);

	
	SelectObject(hdc, hOld);
	ReleaseDC(hwndList, hdc);
}

void DirList_DblClick(HWND hwndList)
{
	static TCHAR szPath[MAX_PATH];
	static TCHAR szTemp[MAX_PATH];
	int idx = SendMessage(hwndList, LB_GETCURSEL, 0, 0);
	int i;
	int len = 0;
	int nLevel, nState;
	
	szPath[0] = _T('\0');
	
	for(i = 0; i < idx; i++)
	{
		GetItemState(hwndList, i, &nLevel, &nState);

		if(nState == FOLDER_OPENED) 
		{
			SendMessage(hwndList, LB_GETTEXT, i, (LONG)szTemp);
			lstrcat(szPath, szTemp);
			len += lstrlen(szTemp);
			
			if(len > 0 && szPath[len-1] != _T('\\'))
			{
				lstrcat(szPath, _T("\\"));
				len++;
			}
		}
	}
	
	SendMessage(hwndList, LB_GETTEXT, i, (LONG)szTemp);
	lstrcat(szPath, szTemp);

	DirList_SetPath(hwndList, szPath);
}

void PostCmdNotify(HWND hwnd, UINT cmd)
{
	PostMessage(
		GetParent(hwnd),
		WM_COMMAND,
		MAKEWPARAM(GetWindowLong(hwnd, GWL_ID), cmd),
		(LONG)hwnd
		);
}

LRESULT CALLBACK DirListProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	DirList *dirlist = (DirList *)GetWindowLong(hwnd, GWL_USERDATA);
	WNDPROC oldproc = dirlist->oldproc;

	switch(msg)
	{
	case WM_NCDESTROY:
		free(dirlist);
		break;

	case WM_GETDLGCODE:
		//A list-box normally returns DLGC_WANTARROWS|DLGC_WANTCHARS
		return DLGC_WANTALLKEYS;

	case WM_KEYDOWN:
		if(wParam == VK_RETURN)
		{
			DirList_DblClick(hwnd);
			PostCmdNotify(hwnd, LBN_DIRCHANGE);
		}
		if(wParam == VK_TAB)
		{
			SetFocus(GetNextDlgTabItem(GetParent(hwnd), hwnd, FALSE));
		}
		if(wParam == VK_ESCAPE)
		{
			PostMessage(GetParent(hwnd), 
				WM_COMMAND, 
				MAKEWPARAM(IDCANCEL, GetWindowLong(hwnd, GWL_ID)), 
				(LONG)hwnd);
		}
		break;

	case WM_LBUTTONDBLCLK:
		DirList_DblClick(hwnd);
		PostCmdNotify(hwnd, LBN_DIRCHANGE);
		break;
	}

	return CallWindowProc(oldproc, hwnd, msg, wParam, lParam);
}

void MakeDirList(HWND hwndList, TCHAR szInitialPath[])
{
	TCHAR szPath[MAX_PATH];
	DirList *dirlist;
	
	SHFILEINFO shfi;
	UINT uFlags;
		
	//Get a valid directory name that we know exists
	GetWindowsDirectory(szPath, MAX_PATH);

	//Get a handle to the system image list
	hDirImgList = GetSystemImageList();

	//Get the icon index for a closed folder
	uFlags = SHGFI_ICON | SHGFI_SMALLICON;
	SHGetFileInfo(szPath, 0, &shfi, sizeof(SHFILEINFO), uFlags);
	nImgFolderClosed = shfi.iIcon;

	//Get the icon index for an opened folder
	uFlags |= SHGFI_OPENICON;
	SHGetFileInfo(szPath, 0, &shfi, sizeof(SHFILEINFO), uFlags);
	nImgFolderOpened = shfi.iIcon;

	DirList_SetPath(hwndList, szInitialPath);

	dirlist = (DirList *)malloc(sizeof(DirList));
	dirlist->oldproc = (WNDPROC)SetWindowLong(hwndList, GWL_WNDPROC, (LONG)DirListProc);
	SetWindowLong(hwndList, GWL_USERDATA, (LONG)dirlist);
}

void DirList_AddItem(HWND hwndList, TCHAR *szSubDir, int level, int state)
{
	int idx = SendMessage(hwndList, LB_INSERTSTRING, -1, (LPARAM)szSubDir);
	SetItemState(hwndList, idx, level, state);
}

void DirList_AddSubDirs(HWND hwndList, TCHAR *szPath, int level)
{
	HANDLE hFind;
	WIN32_FIND_DATA win32fd;
	TCHAR szSearchPath[_MAX_PATH];
	
	lstrcpy(szSearchPath, szPath);
	
	if(szPath[lstrlen(szPath) - 1] != _T('\\'))
		lstrcat(szSearchPath, _T("\\"));

	lstrcat(szSearchPath, _T("*.*"));
	
	if((hFind = FindFirstFile(szSearchPath, &win32fd)) == INVALID_HANDLE_VALUE) 
	{
		return;
	}

	do
	{
		if(win32fd.cFileName[0] != _T('.') && (win32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !(win32fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
		{
			DirList_AddItem(hwndList, win32fd.cFileName, level, FOLDER_CLOSED);
		}

	} while(FindNextFile(hFind, &win32fd) != 0);

	FindClose(hFind);
}

static TCHAR *lstrchr(TCHAR *str, TCHAR c)
{
	//do the search
	while(*str && *str != c) str++;
	if(*str == _T('\0'))		return (TCHAR *)0;
	else					return (TCHAR *)str;

	return 0;
}

BOOL DirList_SetPath(HWND hwndList, TCHAR *szPath)
{
	char szFullPath[MAX_PATH];
	char szText[MAX_PATH];
	int items = 0;
	char *szName;
	char *szSep = szFullPath;

	GetFullPathName(szPath, MAX_PATH, szFullPath, &szName);

	SendMessage(hwndList, LB_RESETCONTENT, 0, 0);

	szSep = strchr(szSep, _T('\\'));

	if(!szSep)
		lstrcpy(szText, szFullPath);
	else
		lstrcpyn(szText, szFullPath, szSep-szFullPath+2);
	
	DirList_AddItem(hwndList, szText, items++, FOLDER_OPENED);

	while(szSep && *(szSep+1) != _T('\0'))
	{
		TCHAR *szOld = szSep;


		szSep = strchr(szSep+1, _T('\\'));

		if(!szSep)
			lstrcpy(szText, szOld+1);
		else
			lstrcpyn(szText, szOld+1, szSep-szOld);
			
		DirList_AddItem(hwndList, szText, items++, FOLDER_OPENED);
	} 

	//Add the sub-directories
	DirList_AddSubDirs(hwndList, szFullPath, items);

	SendMessage(hwndList, LB_SETCURSEL, items-1, 0);

	DirList_UpdateWidth(hwndList);

	return TRUE;
}

BOOL DirList_GetPath(HWND hwndList, TCHAR *szPath, int cchMaxPath)
{
	static char szTemp[MAX_PATH];
	int count = SendMessage(hwndList, LB_GETCOUNT, 0, 0);
	int i;
	int len = 0;
	int nLevel, nState;

	szPath[0] = _T('\0');

	for(i = 0; i < count; i++)
	{
		GetItemState(hwndList, i, &nLevel, &nState);

		if(nState == FOLDER_OPENED)
		{
			SendMessage(hwndList, LB_GETTEXT, i, (LONG)szTemp);

			len += lstrlen(szTemp);

			if(len > cchMaxPath)
				return FALSE;

			lstrcat(szPath, szTemp);
			
			//separate each path component with a back-slash
			if(len > 0 && szPath[len-1] != '\\')
			{
				szPath[len]   = _T('\\');
				szPath[len+1] = _T('\0');
				len++;
			}
		}
		else
		{
			break;
		}
	}

	//remove the last trailing back-slash
	if(len >= 2 && szPath[len-2] != _T(':'))
	{
		szPath[len-1] = _T('\0');
	}

	return TRUE;
}


BOOL DirListMeasure(HWND hwnd, UINT uCtrlId, MEASUREITEMSTRUCT *mis)
{
	HFONT hFont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0);

	mis->itemHeight = DirList_GetLineHeight(hwnd, hFont, nBitmapHeight - 1);
	
	//calculate the y-adjustment when text is taller than bitmap
	nBitmapYOff = (mis->itemHeight - (nBitmapHeight-1)) / 2;		

	return TRUE;
}


BOOL DirListDraw(HWND hwnd, UINT uCtrlId, DRAWITEMSTRUCT *dis)
{
	HWND hwndCombo = GetDlgItem(hwnd, uCtrlId);
	TCHAR szText[MAX_PATH];
	int idx;
	int nState, nLevel;
	
	switch(dis->itemAction)
	{
	case ODA_FOCUS:
		
		//Windows 2000 doesn't display a control's focus in 
		//a dialog box until the ALT key is pressed.
		if(!(dis->itemState & ODS_NOFOCUSRECT))
			DrawFocusRect(dis->hDC, &dis->rcItem);
		
		break;

	case ODA_SELECT:

	case ODA_DRAWENTIRE:

		// get the text string to display, and the item state.
		SendMessage(hwndCombo, LB_GETTEXT, dis->itemID, (LONG)szText);
   		GetItemState(hwndCombo, dis->itemID, &nLevel, &nState);
	
		if(dis->itemState & ODS_SELECTED)
		{
			SetTextColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
			SetBkColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHT));
		}
		else
		{
			SetTextColor(dis->hDC, GetSysColor(COLOR_WINDOWTEXT));
			SetBkColor(dis->hDC, GetSysColor(COLOR_WINDOW));
		}

		//draw the item text first of all. The ExtTextOut function also
		//lets us draw a rectangle under the text, so we use this facility
		//to draw the whole line at once.
		ExtTextOut(dis->hDC, 
			dis->rcItem.left + nBitmapXSpace + nBitmapWidth + nLevel * CX_OFFSET, 
			dis->rcItem.top + 1, 
			ETO_OPAQUE, &dis->rcItem, szText, lstrlen(szText), 0);

		idx = nState == FOLDER_OPENED ? nImgFolderOpened : nImgFolderClosed;

		//draw the folder image, using an icon from an image list.
		ImageList_Draw(hDirImgList, idx, dis->hDC, 
			dis->rcItem.left + 2 + nLevel * CX_OFFSET, 
			dis->rcItem.top + nBitmapYOff, 
			ILD_TRANSPARENT);

		break;
	}

	return TRUE;
}

